package indi.zhifa.recipe.bailan.framework.auth.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import indi.zhifa.recipe.bailan.framework.auth.dao.IBaseAuthNodeRelevantDao;
import indi.zhifa.recipe.bailan.framework.auth.entity.dto.init.AuthNodeCfg;
import indi.zhifa.recipe.bailan.framework.auth.entity.po.BaseAuthNodeEntity;
import indi.zhifa.recipe.bailan.framework.auth.entity.po.BaseDomainEntity;
import indi.zhifa.recipe.bailan.framework.auth.entity.vo.BaseDepartmentNodeVo;
import indi.zhifa.recipe.bailan.framework.auth.entity.vo.BaseDepartmentWithChildrenVo;
import indi.zhifa.recipe.bailan.framework.auth.entity.vo.BaseDomainVo;
import indi.zhifa.recipe.bailan.framework.auth.entity.vo.BaseDomainWithRootVo;
import indi.zhifa.recipe.bailan.framework.auth.service.IBaseAuthNodeService;
import indi.zhifa.recipe.bailan.framework.common.entity.exception.ServiceException;
import indi.zhifa.recipe.bailan.framework.common.util.DtoEntityUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@RequiredArgsConstructor
public abstract class BaseAuthNodeServiceImpl implements IBaseAuthNodeService {

    private final IBaseAuthNodeRelevantDao mDepartmentRelevantDao;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Map<String, BaseDomainVo> init(JSONArray pDomainConfigs){
        Map<String, BaseDomainVo> rtn = new HashMap<>();

        // 处理领域和部门
        for (int i = 0; i < pDomainConfigs.size(); i++) {
            JSONObject jsonObject = pDomainConfigs.getJSONObject(i);
            String domainCode = jsonObject.getString("code");
            BaseDomainEntity baseDomainEntity;
            Long domainId = mDepartmentRelevantDao.domain_findByCode(domainCode);
            if (null != domainId) {
                baseDomainEntity = mDepartmentRelevantDao.domain_check(domainId);
                List<BaseAuthNodeEntity> descendant = mDepartmentRelevantDao.authNode_listByDomainId(domainId);
                BaseDomainVo baseDomainVo = DtoEntityUtil.trans(baseDomainEntity,BaseDomainVo.class);
                Map<String, BaseAuthNodeEntity> departmentEntityMap = baseDomainVo.getDepartmentEntityMap();
                for(BaseAuthNodeEntity baseAuthNodeEntity : descendant){
                    departmentEntityMap.put(baseAuthNodeEntity.getCode(), baseAuthNodeEntity);
                }
                rtn.put(domainCode,baseDomainVo);
                continue;
            }

            baseDomainEntity = mDepartmentRelevantDao.domain_load(jsonObject);
            baseDomainEntity.createInit();
            baseDomainEntity.setCode(domainCode);
            mDepartmentRelevantDao.authNode_save(baseDomainEntity);
            BaseDomainVo baseDomainVo = DtoEntityUtil.trans(baseDomainEntity,BaseDomainVo.class);
            Map<String, BaseAuthNodeEntity> departmentEntityMap = new HashMap<>();
            baseDomainVo.setDepartmentEntityMap(departmentEntityMap);
            rtn.put(baseDomainVo.getCode(),baseDomainVo);

            // 处理部门
            AuthNodeCfg authNodeCfg = new AuthNodeCfg();//创建命令
            authNodeCfg.setCode("rt");
            authNodeCfg.setCascadeCode("rt");
            authNodeCfg.setParentCode("rt");
            authNodeCfg.setParentId(-1L);
            authNodeCfg.setCfg(jsonObject);
            Queue<AuthNodeCfg> que = new ArrayDeque<>();
            que.add(authNodeCfg);

            List<BaseAuthNodeEntity> savingBaseAuthNodeEntityList = new ArrayList<>();
            List<AuthNodeCfg> authNodeCfgList = new ArrayList<>();
            while (!que.isEmpty()) {
                AuthNodeCfg curCfg = que.poll();
                BaseAuthNodeEntity baseAuthNodeEntity = mDepartmentRelevantDao.authNode_load(curCfg.getCfg());
                baseAuthNodeEntity.setDomainId(baseDomainEntity.getId());
                baseAuthNodeEntity.setDomainCode(domainCode);
                baseAuthNodeEntity.setParentId(curCfg.getParentId());
                baseAuthNodeEntity.setParentCode(curCfg.getParentCode());
                baseAuthNodeEntity.setCode(curCfg.getCode());
                baseAuthNodeEntity.setCascade(curCfg.getCascadeCode());
                savingBaseAuthNodeEntityList.add(baseAuthNodeEntity);
                authNodeCfgList.add(curCfg);
                JSONObject curJsonCfg = curCfg.getCfg();
                JSONArray childrenCfg = curJsonCfg.getJSONArray("children");
                if (!CollectionUtils.isEmpty(childrenCfg)) {
                    for (int j = 0; j < childrenCfg.size(); j++) {
                        JSONObject childJsonCfg = childrenCfg.getJSONObject(j);
                        AuthNodeCfg childCfg = new AuthNodeCfg();
                        childCfg.setCfg(childJsonCfg);
                        childCfg.setCode(childJsonCfg.getString("code"));
                        childCfg.setParentId(baseAuthNodeEntity.getId());
                        childCfg.setParentCode(baseAuthNodeEntity.getCode());
                        childCfg.setCascadeCode(curCfg.getCascadeCode() + "-" + childJsonCfg.getString("code"));
                        que.offer(childCfg);
                    }
                }
            }
            for (BaseAuthNodeEntity baseAuthNodeEntity : savingBaseAuthNodeEntityList) {
                departmentEntityMap.put(baseAuthNodeEntity.getCode(), baseAuthNodeEntity);
                if(!mDepartmentRelevantDao.authNode_exist(baseDomainEntity.getId(),baseAuthNodeEntity.getCode())){
                    mDepartmentRelevantDao.authNode_save(baseAuthNodeEntity);
                }
            }
            onInit(domainCode,authNodeCfgList,savingBaseAuthNodeEntityList);
        }
        return rtn;
    }

    protected abstract void onInit(String pDomainCode, List<AuthNodeCfg> pAuthNodeCfgList,List<BaseAuthNodeEntity> pSavingBaseAuthNodeEntityList);

    @Override
    public BaseDomainEntity domainInfo(Long pId) {
        return mDepartmentRelevantDao.domain_check(pId);
    }

    @Override
    public BaseAuthNodeEntity info(Long pId) {
        return mDepartmentRelevantDao.authNode_check(pId);
    }

    @Override
    public BaseDepartmentNodeVo infoTree(Long pId) {
        BaseAuthNodeEntity rootEntity = mDepartmentRelevantDao.authNode_check(pId);
        BaseDepartmentNodeVo rtn = new BaseDepartmentNodeVo(rootEntity);
        BaseDepartmentNodeVo curNode = rtn;
        Queue<BaseDepartmentNodeVo> que = new ArrayDeque<>();
        que.offer(curNode);
        while (!que.isEmpty()){
            curNode = que.poll();
            BaseAuthNodeEntity curBaseAuthNodeEntity = curNode.getDepartment();
            List<BaseAuthNodeEntity> children = mDepartmentRelevantDao.authNode_listByParentId(curBaseAuthNodeEntity.getId());
            if(!CollectionUtils.isEmpty(children)){
                List<BaseDepartmentNodeVo> childrenVos = curNode.getChildren();
                for(BaseAuthNodeEntity child : children){
                    BaseDepartmentNodeVo baseDepartmentNodeVo = new BaseDepartmentNodeVo(child);
                    childrenVos.add(baseDepartmentNodeVo);
                }
                for(BaseDepartmentNodeVo childVo : childrenVos){
                    que.offer(childVo);
                }
            }
        }
        return rtn;
    }

    @Override
    public BaseDepartmentWithChildrenVo infoWithChildren(Long pId){
        BaseAuthNodeEntity rootEntity = mDepartmentRelevantDao.authNode_check(pId);
        BaseDepartmentWithChildrenVo baseDepartmentWithChildrenVo = new BaseDepartmentWithChildrenVo(rootEntity);
        List<BaseAuthNodeEntity> children = mDepartmentRelevantDao.authNode_listByParentId(pId);
        baseDepartmentWithChildrenVo.setChildren(children);
        return baseDepartmentWithChildrenVo;
    }

    @Override
    public List<BaseDomainWithRootVo> listBaseDomains(){
        List<BaseDomainEntity> domainEntityList = mDepartmentRelevantDao.domain_list();
        List<BaseDomainWithRootVo> baseDomainWithRootVoList = new ArrayList<>();
        for(BaseDomainEntity baseDomainEntity : domainEntityList){
            BaseDomainWithRootVo baseDomainWithRootVo = new BaseDomainWithRootVo();
            baseDomainWithRootVoList.add(baseDomainWithRootVo);
            Long depId = mDepartmentRelevantDao.authNode_findByDomainAndCode(baseDomainEntity.getId(),"rt");
            BaseAuthNodeEntity baseAuthNodeEntity = mDepartmentRelevantDao.authNode_check(depId);
            baseDomainWithRootVo.setDomainEntity(baseDomainEntity);
            baseDomainWithRootVo.setRoot(baseAuthNodeEntity);
        }
        return baseDomainWithRootVoList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public BaseAuthNodeEntity create(Long pParentId, BaseAuthNodeEntity pSavingBaseAuthNodeEntity) {
        BaseAuthNodeEntity parent = mDepartmentRelevantDao.authNode_check(pParentId);
        if(mDepartmentRelevantDao.authNode_exist(parent.getDomainId(), pSavingBaseAuthNodeEntity.getCode())){
            throw new ServiceException("领域"+ pSavingBaseAuthNodeEntity.getDomainCode()+"以存在code为"+ pSavingBaseAuthNodeEntity.getCode()+"的部门");
        }
        pSavingBaseAuthNodeEntity.setParentId(pParentId);
        pSavingBaseAuthNodeEntity.setParentCode(parent.getCode());
        pSavingBaseAuthNodeEntity.setCascade(parent.getCascade()+"-"+ pSavingBaseAuthNodeEntity.getCode());
        pSavingBaseAuthNodeEntity.setDomainId(parent.getDomainId());
        pSavingBaseAuthNodeEntity.setDomainCode(parent.getCode());
        mDepartmentRelevantDao.authNode_save(pSavingBaseAuthNodeEntity);
        return pSavingBaseAuthNodeEntity;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public BaseDepartmentNodeVo reRoot(Long pParentId, Long pId) {
        BaseAuthNodeEntity editingDepartmentEntity = mDepartmentRelevantDao.authNode_check(pId);
        // 可行性测试
        List<BaseAuthNodeEntity> descendants = mDepartmentRelevantDao.authNode_listDescendants(editingDepartmentEntity.getCascade());
        Set<Long> descendantIdSet = descendants.stream().map(BaseAuthNodeEntity::getId).collect(Collectors.toSet());
        if(descendantIdSet.contains(pParentId)){
            throw new ServiceException("不能接到子孙节点");
        }

        BaseAuthNodeEntity parent = mDepartmentRelevantDao.authNode_check(pParentId);
        List<BaseAuthNodeEntity> editingDepartmentEntityList = new ArrayList<>();
        editingDepartmentEntity.setParentId(parent.getId());
        editingDepartmentEntity.setParentCode(parent.getCode());
        editingDepartmentEntity.setCascade(parent.getCascade()+"-"+editingDepartmentEntity.getCode());
        editingDepartmentEntityList.add(editingDepartmentEntity);
        Queue<BaseDepartmentNodeVo> queue = new ArrayDeque<>();
        BaseDepartmentNodeVo rtn = new BaseDepartmentNodeVo(editingDepartmentEntity);
        queue.offer(rtn);
        BaseDepartmentNodeVo current = null;
        while(!queue.isEmpty()){
            current = queue.poll();
            BaseAuthNodeEntity baseAuthNodeEntity = current.getDepartment();
            List<BaseAuthNodeEntity> children = mDepartmentRelevantDao.authNode_listByParentId(baseAuthNodeEntity.getId());
            List<BaseDepartmentNodeVo> childrenVo = current.getChildren();
            for(BaseAuthNodeEntity child : children){
                child.setCascade(baseAuthNodeEntity.getCascade()+"-"+child.getCode());
                editingDepartmentEntityList.add(child);
                BaseDepartmentNodeVo childVo = new BaseDepartmentNodeVo(child);
                childrenVo.add(childVo);
                queue.offer(childVo);
            }
        }
        for(BaseAuthNodeEntity departmentEntity : editingDepartmentEntityList){
            mDepartmentRelevantDao.authNode_update(departmentEntity.getId(),departmentEntity);
            onDepCodeChange(departmentEntity);
        }
        return rtn;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean delete(Long pId) {
        BaseAuthNodeEntity root = mDepartmentRelevantDao.authNode_check(pId);
        List<BaseAuthNodeEntity> descendants = mDepartmentRelevantDao.authNode_listDescendants(root.getCascade());
        for(BaseAuthNodeEntity baseAuthNodeEntity : descendants){
            BaseAuthNodeEntity deletedDepartmentEntity = mDepartmentRelevantDao.authNode_delete(baseAuthNodeEntity.getId());
            onDepDelete(deletedDepartmentEntity);
        }
        return true;
    }

    abstract protected void onDepCodeChange(BaseAuthNodeEntity pBaseAuthNodeEntity);

    abstract protected void onDepDelete(BaseAuthNodeEntity pBaseAuthNodeEntity);

}
